package genome;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;

import tool.RandomGenerator;
import tool.RepeatRandom;
import tool.Utility;


//A version of CGP for generating fixed vector output 

public class CGP extends Genotype {
	
	public int NUM_FUNCTIONS;
	private FunctionSet funcSet;
	
	public static double MUTATION_RATE = GenomeParameters.CGP_MUTATION_RATE;
	public int arity; //number of arguments per function
	public static double[] fixedVector = {1,1};

	private RepeatRandom r = RandomGenerator.R;
	

	public static void main(String[] args) throws IOException {
		org.junit.runner.JUnitCore.runClasses(CGPTest.class);
	}
	
	
	public CGP(double[] in, int sz){
		this(in,sz,GenomeParameters.CGP_FUNCTION_SET);
	}
	
	public CGP(double[] in, int sz, FunctionSet fSet) {
		super(in, sz);
		this.funcSet = fSet;
		this.NUM_FUNCTIONS = fSet.NUM_FUNCTIONS;
		this.arity = fSet.ARITY;
		
		if(sz % (arity+1) >0.0){System.out.println("CGP genotype length for arity "+arity+" functions must be divisable by "+(arity+1)); System.exit(0);}
		for(int i=0; i<sz;i=i+(arity+1)){
			for(int j=0;j<arity;j++){
				
					genome[i+j] = r.nextInt(i/(arity+1)+in.length); //generate 
			}
			genome[i+arity] = r.nextInt(NUM_FUNCTIONS);
		}
	}
	
	@Override
	public void clear(int x){
		//sets every member of array below i to zero
		for(int i=0;i<x;i++){
			this.genome[i] = 0;
		}
	}
	
	@Override
	public double distanceTo(Genotype g){
		//Assume a CGP genotype, but left generic for compatibility
		
		double m = 1.0;
		double p0 = 1-MUTATION_RATE;
		
		int x = arity+1;
		for (int i = 0; i < g.size/x; i++) {
			int n;
			for(int j=0;j<arity;j++){
				n = i*x+j;
				double allele1 = this.genome[n];
				double allele2 = g.genome[n];
				
				double s = MUTATION_RATE/(i+input.length);
				
				if(allele1 == allele2){
					m = m*(p0+s); //likelihood of keeping the same
				}
				else {
					m = m*s; //likelihood of mutating to matching connection
				}
			}
			n = i*x+arity;
			double allele1 = this.genome[n];
			double allele2 = g.genome[n];
			
			double s = MUTATION_RATE/(NUM_FUNCTIONS);
			
			if(allele1 == allele2){
				m = m*(p0+s); //likelihood of keeping the same
			}
			else {
				m = m*s; //likelihood of mutating to matching connection
			}
		}
		return Utility.roundToSignificantFigures(m, 5);
	}
	
	public boolean equals(CGP g){
		return (super.equals(g) && (g instanceof CGP));
	}
	
	@Override
	
	public double eval(){
		return eval(size/(arity+1)+ input.length - 1);
	}
	
	public double eval(int x) {
			if(x<input.length){return input[x];} //return an input if less than number of inputs
			else{
				int[] gene = new int[arity+1]; //otherwise pass to func for next evaluations
				int pos = (x - input.length)*(arity+1);
				//System.out.println("pos "+pos);
				gene[0] = genome[pos];
				gene[1] = genome[pos+1];
				gene[2] = genome[pos+2];
				
				double out = func(gene);
				return out;
			}
		}
	
	
	
	@Override
	public ArrayList<Genotype> generateSpace() {
		//uses the specification of this CGP genome to return the whole possible space of programs
		//obviously this will take impossibly long for any real set.
		
		ArrayList<Genotype> omega = new ArrayList<Genotype>();
		
		CGP g = new CGP(this.input,this.size);
		g.clear(g.size);
		CGP gOld=null;
		int i=0;
		while(!g.equals(gOld)){
			omega.add(g);
			gOld = g.getCopy();
			g = (CGP)g.increment();
			i=i+1;
		}
		return omega;
	}
	
	public double func(int[] connect){

		double[] vals = new double[connect.length-1];
		for(int i=0;i<vals.length;i++){
			vals[i] = eval(connect[i]);
		}
		
		return funcSet.func(vals, connect[arity]);
	}
	
	@Override
	public ArrayList<Genotype> generateSpace(int samples) {
		//uses the specification of this CGP genome to return a subset of the whole search space (population), all unique
		
		ArrayList<Genotype> omega = new ArrayList<Genotype>();
		for(int i=0;i<samples;i++){
			CGP g = this.newGenome();
			if(!Utility.contains(omega,g)){
				omega.add(g);
			}
			else {
				i = i-1;
			}
		}
		return omega;
	}
	
	@Override
	public String getConstantString() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public CGP getCopy() {
		double[] in = new double[input.length];
		System.arraycopy(input,0,in,0,input.length);
		CGP copy = new CGP(in,size);
		System.arraycopy(this.genome,0,copy.genome,0,this.genome.length);
		copy.setRELATIVE_FITNESS(RELATIVE_FITNESS);
		
		return copy;
	}
	
	public String getfString(int[] connect){
	
		int x = connect.length-1;
		
		String[] s = new String[x];
		
		for (int i = 0; i < x; i++) {
			s[i] = toMathString(connect[i]);
		}
		return funcSet.getfString(s, connect[x]);
	}
		

	@Override
	public double[] getSymbolicValues(){
		return getSymbolicValues(8,0.0,Math.PI); //DEFAULTS
	}
	
	public double[] getSymbolicValues(int nObjectives,double lowerBound,double upperBound){
		double[] vals = new double[nObjectives];
		double j=lowerBound;
		this.input[1] = 1.0;
		double interval=(upperBound-lowerBound)/(nObjectives-1);
			for(int n=0;n<nObjectives;n++){
				this.input[0]=j;
				vals[n]=Utility.roundToSignificantFigures(this.eval(),5);
				j = j+interval;
			}
			
		//check for machine error
			for (int i = 0; i < vals.length; i++) {
				double tol = 0.00000001;
				if((vals[i] < tol && vals[i] > -tol)){
					vals[i] = 0.0;
				}
			}
		return vals;
	}
	
	@Override
	public Genotype increment(){
		//routine that returns an incremented copy of the CGP program
			CGP g = this.getCopy();
			boolean isIncremented = false;
			int i = 0;
			int gSize = arity+1;
			while(!isIncremented && i<this.size){
				int gX = genome[i];
				int allele = (i+1)%gSize;
				int gene = i/gSize;
				switch(allele){
					case 0:
						if(gX<NUM_FUNCTIONS-1){
							g.genome[i] = genome[i]+1;
							g.clear(i);
							isIncremented = true;
						}
						else {
							i = i+1;
						}
					break;
					
					default:
						if(gX<(gene+g.input.length-1)){
							g.genome[i] = genome[i]+1;
							g.clear(i);
							isIncremented = true;
						}
						else {
							i = i+1;
						}
					break;
				}
			}
			return g;
		}
	
	public CGP newGenome(){
		//as copy routine, but generates a new individual
		double[] in = new double[input.length];
		System.arraycopy(input,0,in,0,input.length);
		CGP copy = new CGP(in,size);
		return copy;
	}
	
	@Override
	public CGP pointMutate() {
		
		CGP g = this.getCopy();
		
		int gene = r.nextInt(this.size/(arity+1));
		int allele = r.nextInt(arity+1);
		if(allele<(arity)){
				g.genome[gene*(arity+1)+allele] = r.nextInt(gene+input.length);
		}
		else {
			g.genome[gene*(arity+1)+allele] = r.nextInt(NUM_FUNCTIONS);
		}
		
		return g;
	}

	@Override
	public void setConstants(double[] d) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public Genotype stoMutate(){
		CGP g = this.getCopy();
		
		for(int gene=0;gene<this.size/(arity+1);gene++){
			for(int allele = 0; allele < arity+1; allele++){
				if(r.nextDouble()<=MUTATION_RATE){	
						
						int connections = gene+input.length;
						int index = gene*(arity+1)+allele;
						boolean mutated = false;
						
						int x = g.genome[index];
						
						while(!mutated){
							if(allele<(arity)){
								
								int xprime = r.nextInt(connections);
								if(connections==1 || x !=xprime){
									g.genome[index] = xprime;
									mutated = true;
								}
							}
							else {
								int xprime = r.nextInt(NUM_FUNCTIONS);
								if(x!=xprime || NUM_FUNCTIONS==1){
									g.genome[index] = xprime;
									mutated = true;
								}
							}
						}
				}
			}
		}
		
		return g;
	}

	@Override
	public String toMathString() {
		return toMathString(size/(arity+1)+ input.length - 1);
	}

	public String toMathString(int x){
			
			if(x<input.length){return "["+x+"]";}
			
			else{
				int[] gene = new int[arity+1];
				int pos = (x - input.length)*(arity+1);
				gene[0] = genome[pos];
				gene[1] = genome[pos+1];
				gene[2] = genome[pos+2];
				
				return getfString(gene);
			}
	}
	
	public String toSymbolicValues(int nObjectives,double lowerBound,double upperBound){
		String s = "";
		double[] x = getSymbolicValues(nObjectives,lowerBound,upperBound);
		for (int i = 0; i < x.length; i++) {
			s = s+" "+x[i];
		}
		return s;
	}
	
	@Override
	public double[] toVector(){
		double[] vNew = new double[fixedVector.length];
		for(int i=0;i<fixedVector.length;i++){
			double vi = fixedVector[i];
			this.input[0] = vi;
			this.input[1] = i;
			vNew[i] = this.eval();
		}
		return vNew;
	}


	private static void spaceOutput(int objectives, double lBound, double uBound, String fName) throws IOException {
		//a manual test method used to output the whole space, with samples evaluated over a range of points.
		//used in symbolic value test
		
		double[] in = {1.0,1.0};
		CGP g = new CGP(in,6);
		
		ArrayList<Genotype> gspace = g.generateSpace();
				
		PrintWriter p = new PrintWriter(new FileWriter(new File("./"+fName+".dat")));
		
		for(Genotype ind:gspace){
			String out = "";
			double[] vals = ((CGP)ind).getSymbolicValues(objectives,lBound,uBound);
			for (int i = 0; i < vals.length; i++) {
				out = out+" "+vals[i];
			}
			p.println(ind+" "+ind.toMathString()+" "+out);
		}
		p.println("Space size: "+gspace.size());
		p.close();
	}


	private static void symbolicValueTest(CGP g) throws IOException {
		//Symbolic Value test
		
		int[] gGenome = {1,0,1,2,0,4};
		g.genome=gGenome;
		System.out.println(g.toMathString());
		double[] x = g.getSymbolicValues(5, 0, Math.PI);
		for (int i = 0; i < x.length; i++) {
			System.out.println(x[i]);
		}
		
		spaceOutput(2,0.0,Math.PI,"CGPSpace_2");
	}

}
